//本代码由汪碧康编写于2014年8月26日
//pconline.com.cn
//用于方便查看本机性能参数及远程查看服务器性能参数
// xmonitor.go
//v1.1
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"

	//"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const (
	ServerPort = "9099"

//	MailAdrres   = "192.168.xx.xx"       //smtp server
//	MailAccount  = "xxx@pconline.com.cn" //the Sender Account
//	MailPassword = ""                           //Sender Account's Password
//	ReceiveMail  = "xxx@pconline.com.cn"         //Receive account
)

//get load
func readload() string {
	fi, err := os.Open("/proc/loadavg")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	ldnow := strings.Split(string(fd), " ")
	return ldnow[0]
}

//get mem
func Regexpstr(str string, re []byte) float64 {
	reg := regexp.MustCompile(str)
	regstr := strings.Split(reg.FindString(string(re)), ":")
	regint, err := strconv.ParseFloat(strings.Trim(regstr[1], " "), 64)
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	return regint
}

func readmem() string {
	fi, err := os.Open("/proc/meminfo")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	MemFree := Regexpstr(`MemFree(.*)[0-9]`, fd) + Regexpstr(`Buffers(.*)[0-9]`, fd) + Regexpstr(`Cached(.*)[0-9]`, fd)
	Useage := 1 - MemFree/Regexpstr(`MemTotal(.*)[0-9]`, fd)
	meminfo := fmt.Sprintf("%.2f|%.2fG", Useage, MemFree/1024/1024)
	return meminfo
}

//get swap
func readswap() string {
	fi, err := os.Open("/proc/meminfo")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	SwapFree := Regexpstr(`SwapFree(.*)[0-9]`, fd)
	Useage := 1 - SwapFree/Regexpstr(`SwapTotal(.*)[0-9]`, fd)
	swapinfo := fmt.Sprintf("%.2f|%.2fG", Useage, SwapFree/1024/1024)
	return swapinfo

}

//get traffic
func Geteth() string {
	fi, err := os.Open("/proc/net/route")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	reg := regexp.MustCompile(`eth[0-9]\s00000000(.*)`)
	regstr1 := reg.FindString(string(fd))
	reg2 := regexp.MustCompile(`eth[0-9]`)
	regstr2 := reg2.FindString(regstr1)
	return regstr2
}

func GetTraffic(xeth string) (float64, float64) {
	fi, err := os.Open("/proc/net/dev")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	reg := regexp.MustCompile(xeth + "(.*)")
	regstr := reg.FindString(string(fd))
	trnum := strings.Fields(regstr)
	tin, _ := strconv.ParseFloat(strings.Split(trnum[0], ":")[1], 64)
	tout, _ := strconv.ParseFloat(trnum[8], 64)
	return tin, tout

}

func CalTraffic(xeth string) string {
	a, b := GetTraffic(xeth)
	time.Sleep(1000 * time.Millisecond)
	a1, b1 := GetTraffic(xeth)
	//fmt.Printf("%8.2f,%8.2f\n", a1,a)
	//fmt.Printf("%8.2f,%8.2f\n", b1,b)
	trafficinfo := fmt.Sprintf("%.2f|%.2f[Mb]", (a1-a)*8/1024/1024, (b1-b)*8/1024/1024)
	return trafficinfo
}

//get disk
func DiskUsage(path string, cc bool) (float64, float64) {
	fs := syscall.Statfs_t{}
	err := syscall.Statfs(path, &fs)
	if err != nil {
		panic(err)
	}
	disk_All := fs.Blocks * uint64(fs.Bsize)
	var disk_Free uint64
	if cc {
		disk_Free = fs.Bfree * uint64(fs.Bsize)
	} else {
		disk_Free = fs.Bfree*uint64(fs.Bsize) - disk_All*5/100
	}
	disk_Used := disk_All - disk_Free
	dusage := float64(disk_Used) / float64(disk_All)
	dfree := float64(disk_Free) / 1024 / 1024 / 1024
	return dusage, dfree
}

func GetPart() (pat []string) {
	fi, err := os.Open("/etc/mtab")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
		return
	}
	defer fi.Close()
	bi := bufio.NewReader(fi)
	for {
		line, err := bi.ReadString('\n')
		if err != nil {
			break
		}
		if strings.Contains(line, "/proc") || strings.Contains(line, "none") || strings.Contains(line, "devpts") || strings.Contains(line, "sysfs") {
			//	fmt.Println("yes")
		} else {
			str := strings.Fields(line)[1]
			pat = append(pat, str)
		}
	}
	return
}

func GetDisk() string {
	arry := GetPart()
	var max float64 = 0.00
	var Diskinfo string

	fi, err := os.Open("/etc/fstab")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	var a, b float64
	for i := 0; i < len(arry); i++ {
		//fmt.Println(arry[i])
		if strings.Contains(string(fd), arry[i]) {
			a, b = DiskUsage(arry[i], false)
		} else {
			a, b = DiskUsage(arry[i], true)
		}
		if a > max {
			max = a
			Diskinfo = fmt.Sprintf("%.2f|%.2fG", a, b)
		}
	}
	return Diskinfo
}

func GetConn() string {
	file, err := os.Open("/proc/net/tcp")
	if err != nil {
		//panic(err)
		fmt.Println(err.Error())
	}
	defer file.Close()
	bi := bufio.NewReader(file)

	//es, te, tss, tsr, tfw1, tfw2, ttw, tc, tcw, tla, tl, tcing := 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
	te, tss, tsr, tfw1, tfw2, ttw := 0, 0, 0, 0, 0, 0
	total := 0
	for {
		line, err := bi.ReadString('\n')
		if err != nil {
			break
		}
		tcpstatus := strings.Fields(line)[3]
		switch tcpstatus {
		//case "00": //ERROR_STATUS
		//	es++
		case "01": //TCP_ESTABLISHED
			te++
		case "02": //TCP_SYN_SENT
			tss++
		case "03": //TCP_SYN_RECV
			tsr++
		case "04": //TCP_FIN_WAIT1
			tfw1++
		case "05": //TCP_FIN_WAIT2
			tfw2++
		case "06": //TCP_TIME_WAIT
			ttw++
			//case "07": //TCP_CLOSE
			//	tc++
			//case "08": //TCP_CLOSE_WAIT
			//	tcw++
			//case "09"://TCP_LAST_ACK
			//	tla++
			//case "0A"://TCP_LISTEN
			//	tl++
			//case "0B"://TCP_CLOSING
			//	tcing++
		}
		total++
	}
	//fmt.Printf("%d %d %d\n", tl, te, tcw)
	//return fmt.Sprintf("ESTABLISHED:%d|SYN_SENT:%d|SYN_RECV:%d|TIME_WAIT:%d|FIN_WAIT:%d|ALL:%d\n",te,ttw,tfw1+tfw2,es+te+tss+tsr+tfw1+tfw2+ttw+tc+tcw+tla+tl+tcing)
	conninfo := fmt.Sprintf(" %d| %d | %d | %d | %d |%d", te, tss, tsr, ttw, tfw1+tfw2, total-1)
	return conninfo
}

func allsysinfo() string {
	allinfo := fmt.Sprintf("%s  %s  %s  %s   %s   %s", readload(), readmem(), readswap(), CalTraffic(Geteth()), GetDisk(), GetConn())
	return allinfo
}

func PrintTitle() string {
	var title string = "--time--  -load- -mem[U|F]-  -swap[U|F]-  -traffic[I|O]-   -disk[U|F]-    -[ES|SS|SR|TW|FW|ALL]-"
	return title
}

func DelLog() {
	d, _ := time.ParseDuration("-24h")
	var dellogtime = time.Now().Add(d * 30).Format("20060102")
	err := os.Remove("/tmp/xmonitor." + dellogtime + ".log")
	if err != nil {
		fmt.Println("delete log xmonitor." + dellogtime + ".log failure!")
	}
}

func PrintLog(logcontent string, t2 int) {
	var logtime = time.Now().Format("20060102")
	var logFileName = "/tmp/xmonitor." + logtime + ".log"
	runtime.GOMAXPROCS(runtime.NumCPU())
	logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("can't write log!")
		return
	}
	defer logFile.Close()
	if t2 == 1 {
		gLogger := log.New(logFile, "", log.Ldate|log.Ltime)
		gLogger.Printf("   %v\n", logcontent)
	} else {
		gLogger1 := log.New(logFile, "", 0)
		gLogger1.Printf("             %v\n", logcontent)
	}

}

func DaemonRun(t1, t2, t3 *time.Ticker) {
	go func() {
		for {
			select {
			case <-t1.C:
				PrintLog(allsysinfo(), 1)
			case <-t2.C:
				DelLog()
			case <-t3.C:
				PrintLog(PrintTitle(), 2)
			}
		}
	}()
	tcpAddr, _ := net.ResolveTCPAddr("tcp4", "0.0.0.0:"+ServerPort)
	tcpListener, err := net.ListenTCP("tcp4", tcpAddr)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer tcpListener.Close()
	for {
		tcpConn, err := tcpListener.Accept()
		if err != nil {
			fmt.Println(err.Error())
		}
		go func() {
			for {
				_, err := tcpConn.Write([]byte(allsysinfo()))
				if err != nil {
					defer tcpConn.Close()
					return
				}
				time.Sleep(4 * time.Second)
			}
		}()
	}
}

/*
func LiveRun(t1 *time.Ticker) {
	tlog := log.New(os.Stdout, "\r", log.Ltime)
	select {
	case <-t1.C:
		tlog.Printf("    %s", allsysinfo())
	}
}*/

/*
func RemoteRun(t1 *time.Ticker) {
	select {
	case <-t1.C:
		log.Println(allsysinfo())
	}
}*/

func ClientRun(serverip string, count int) {
	tcpAddr, _ := net.ResolveTCPAddr("tcp4", serverip+":"+ServerPort)
	client, err := net.DialTCP("tcp4", nil, tcpAddr)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer client.Close()
	buf := make([]byte, 1024)
	tlog := log.New(os.Stdout, "\r", log.Ltime)
	for i := 0; i < count; i++ {
		//log.Println("client")
		c, err := client.Read(buf)
		if err != nil {
			fmt.Println("connect failure")
			return
		} else {
			//fmt.Println(string(buf[0:c_kk]))
			if i%40 == 0 {
				fmt.Println(PrintTitle())
			}
			tlog.Printf(" %s", string(buf[0:c]))
		}
	}
}

func HelpUsage() {
	fmt.Println("Usage: xmonitor [options]")
	fmt.Println("[options]:")
	fmt.Println("      no args               run in live mode")
	fmt.Println("      -d=true               run in daemon mode")
	fmt.Println("      -i=                   specify intervals numbers,it is in seconds,use in live mode; example:-i=3")
	fmt.Println("      -c=                   print three times local info,use in live mode; example:-c=3")
	fmt.Println("      -e=                   see traffic on special network eg:eth0 or eth1")
	fmt.Println("      --ip=                 print remote ip info,if remote ip run in daemon; example:--ip=11.26 or 192.168.11.26")
	fmt.Println("      -s=                   see log in a month,need xmonitor run in daemon mode,log in /tmp; example:-s=3")
	fmt.Println("      -email=true           send warning email,need xmonitor run in daemon mode")
	fmt.Println("      -stop                 stop xmonitor daemon")
	fmt.Println("      -h                    help")
	fmt.Println("[Notes]:")
	fmt.Println("       U:useage,F:free")
	fmt.Println("       ES:ESTABLISHED,SS:SYN_SENT,SR:SYN_RECV,TW:TIME_WAIT,FW:FIN_WAIT")
}

func startprocess(pidpath, args string) (pid int, err error) {
	if _, err := os.Stat(pidpath); err != nil {
		if os.Getppid() != 1 {
			//fmt.Printf("%d", os.Getpid())
			//filePath, _ := filepath.Abs(os.Args[0])
			cmd := exec.Command(os.Args[0], args)
			cmd.Stdin = os.Stdin
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Start()
			fmt.Println("start xmonitor success! [PID]:", cmd.Process.Pid)
			err = ioutil.WriteFile(pidpath, []byte(strconv.Itoa(cmd.Process.Pid)), 0640)
		}
	} else {
		fmt.Println("xmonitor already running")
	}
	return
}

func stopprocess(pidpath string) {
	fi, err := os.Open(pidpath)
	if err != nil {
		//panic(err)
		fmt.Println("read pid failure!")
		return
	}
	defer fi.Close()
	fd, err := ioutil.ReadAll(fi)
	cmd := exec.Command("kill", string(fd))
	cmd.Start()
	err = os.Remove(pidpath)
	if err != nil {
		fmt.Println("delete failure!")
	} else {
		fmt.Println("stop xmonitor success!")
	}
	return
}

func main() {
	var Interval int
	var Rip string
	var Seelog int
	var Count int
	flag.StringVar(&Rip, "ip", "", "client ip")
	//by := flag.String("by", "", "print define field in load,mem,swap,disk,io")
	deveth := flag.String("e", "", "print define eth traffic")
	flag.IntVar(&Seelog, "s", 0, "clear xmonitor log")
	help := flag.Bool("h", false, "show help info")
	runwork := flag.Bool("r", false, "run in daemon mode")
	daemon := flag.Bool("d", false, "run in daemon mode")
	stoppid := flag.Bool("stop", false, "run in daemon mode")
	flag.IntVar(&Interval, "i", 3, "default the value is 3")
	flag.IntVar(&Count, "c", 2880, "The total number of print")
	flag.Parse()

	tlive := time.NewTicker(time.Duration(Interval) * time.Second) //Printing interval
	tdaemon := time.NewTicker(60 * time.Second)                    //record interval in daemon mode
	tdellog := time.NewTicker(24 * time.Hour)                      //del daemon log interval
	ttitle := time.NewTicker(1 * time.Hour)                        //print title in daemon mode

	if *help {
		HelpUsage()
		return
	}

	if *deveth != "" {
		fmt.Printf("                           IN|OUT\n")
		for {
			log.Printf("     %s\n", CalTraffic(*deveth))
			time.Sleep(3 * time.Second)
		}
		return
	}

	if Seelog > 0 {
		d, _ := time.ParseDuration("-24h")
		var logtime string
		if Seelog > time.Now().Day() {
			logtime = time.Now().Add(d*30).Format("200601") + strconv.Itoa(Seelog)
		} else if Seelog < 10 {
			logtime = time.Now().Format("200601") + "0" + strconv.Itoa(Seelog)
		} else {
			logtime = time.Now().Format("200601") + strconv.Itoa(Seelog)
		}
		fi, err := os.Open("/tmp/xmonitor." + logtime + ".log")
		if err != nil {
			fmt.Printf("no NO.%d data,more data view in /tmp dir/\n", Seelog)
			return
		}
		fd, err := ioutil.ReadAll(fi)
		fmt.Println(string(fd))
		fmt.Printf("view NO.%d data\n", Seelog)
		return
	}

	if Rip != "" {

		/*tip := strings.Split(Rip, ",")
		//fmt.Println(len(tip))
		for i := 0; i < len(tip); i++ {
			fmt.Printf("        %s", tip[i])
			//fmt.Print(fmt.SPrint(tip[i]))
			//t1 := fmt.Sprintln(tip[i])
			//fmt.Print(t1)
			fmt.Printf("\n")
			ClientRun(tip[i], Count)
		}
		fmt.Printf("\n") */

		if strings.Contains(Rip, "192.168.") == false {
			//reg:=regexp.MustCompile("1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9]?[0-9])\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9]?[0-9])\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9]?[0-9])\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9]?[0-9]")
			//reg := regexp.MustCompile("(\\d){1,3}.(\\d){1,3}$")
			//if reg.MatchString(Rip) {
			var rip1 string = "192.168." + Rip
			ClientRun(rip1, Count)
		} else {
			var rip1 string = Rip
			ClientRun(rip1, Count)
		}
		//fmt.Println(len(strings.Split(Rip,",")[])
		//fmt.Println(strings.Split(Rip,",")[0])
		return
	}

	if *runwork {
		DaemonRun(tdaemon, tdellog, ttitle)
		return
	}
	if *daemon {
		startprocess("/tmp/xmonitor.pid", "-r=true")
		return
	}
	if *stoppid {
		stopprocess("/tmp/xmonitor.pid")
		return
	}

	//if flag.NFlag() == 0 || (flag.NFlag() == 1 && (Interval >= 1 || Count > 0) && Rip == "") || (flag.NFlag() == 2 && Interval >= 1 && Count > 0 && Rip == "") {

	logdate := time.Now().Format("20060102")
	var args string = "/tmp/xmonitor." + logdate + ".log"
	runout, _ := exec.Command("tail", "-30", args).Output()
	fmt.Printf("%s\n", runout)
	tlog := log.New(os.Stdout, "\r", log.Ltime)
	for i := 0; i < Count; i++ {
		if i%40 == 0 {
			fmt.Println(PrintTitle())
		}
		//LiveRun(tlive)
		select {
		case <-tlive.C:
			tlog.Printf(" %s", allsysinfo())
		}
	}
	//}
}
